package com.djl.supports;

import com.djl.annotation.SqlStatement;
import com.djl.supports.interceptor.ExecutorQueryInterceptor;
import com.djl.supports.interceptor.ParameterInterceptor;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.reflections.Reflections;

import javax.persistence.Column;
import javax.persistence.Entity;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class PluginInjector {

    protected final Log logger = LogFactory.getLog(getClass());

    private static final String QUERY =  "selectByQuery";
    private static final String DELETE =  "delete";
    private static final String UPDATE =  "update";
    private static final String INSERT =  "insert";

    private static final ConcurrentHashMap<String,Object> PROPERTIESMAP = new ConcurrentHashMap<>();

    private static final String NAMESPACE = ".";
    //缓存属性
    private final static ConcurrentHashMap<String, Map<String,Object>> MODEL_REPOSITORY_CACHE = new ConcurrentHashMap<>();

    private static final String SQL = "sql";

    private static final String CONDITION =" where id=#{id}";

    private static final String PRIMARYKEY ="#{id},";

    private LanguageDriver languageDriver;

    private Set<Class<?>> sqlStatementSet;

    private final static List<Interceptor> interceptorList = new ArrayList<>();

    static{
        interceptorList.add(new ParameterInterceptor());
        interceptorList.add(new ExecutorQueryInterceptor());
    }

    public PluginInjector() {
        Reflections reflections = new Reflections(PluginInjector.class.getPackage().getName());
        this.sqlStatementSet = reflections.getTypesAnnotatedWith(SqlStatement.class);
    }


    public void createCommandStatement(Configuration configuration,SqlCommandType sqlCommandType, final Class<?> resultType,String script,String id) {
        this.languageDriver = configuration.getDefaultScriptingLanguageInstance();

        SqlSource sqlSource = languageDriver.createSqlSource(configuration, script, Map.class);
        MappedStatement ms = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).resultMaps(
                new ArrayList<ResultMap>() {
                    {
                        add(new ResultMap.Builder(configuration, "defaultResultMap", resultType, new ArrayList<>(0))
                                .build());
                    }
                }).build();
        // 接口与xml绑定
        configuration.addMappedStatement(ms);
    }

    /**
     * 注入CRUD插件
     */
    public void inject(Configuration configuration, Class<?> repositoryClass) {
        //清空
        PROPERTIESMAP.clear();
        //注入插件
        interceptorList.forEach(configuration::addInterceptor);
        //获取泛型
        Class<?> modelClass = extractModelClass(repositoryClass);

        if(modelClass!=null){
            //判断泛型类是否符合CURD规范
            if(modelClass.isAnnotationPresent(Entity.class)){
                logger.info(repositoryClass+" 已开启 CURD");
                //获取表名
                String table = modelClass.getAnnotation(Entity.class).name();
                //查询属性值
                String field = propertyHandler(modelClass);

                MODEL_REPOSITORY_CACHE.put(table,PROPERTIESMAP);

                for (Class cls:sqlStatementSet){
                    SqlStatement sqlStatement = (SqlStatement)cls.getAnnotation(SqlStatement.class);
                    String value = sqlStatement.value();
                    SqlCommandType sqlCommandType = sqlStatement.commandType();
                    String id = repositoryClass.getName()+NAMESPACE+value;
                    String script = "";
                    try {
                        Method method = cls.getMethod(SQL,String.class,String.class);
                        if(sqlStatement.value().equals(QUERY)){
                            script = (String)method.invoke(cls.newInstance(),table,field);
                        }
                        if(sqlStatement.value().equals(INSERT)){
                            //insert 属性封装
                            String fieldInsert = propertyHandlerInsert(modelClass);
                            script = (String)method.invoke(cls.newInstance(),table,fieldInsert);
                        }
                        if(sqlStatement.value().equals(DELETE)){
                            //delete 属性封装
                            script = (String)method.invoke(cls.newInstance(),table,CONDITION);
                        }
                        if(sqlStatement.value().equals(UPDATE)){
                            //update 属性封装
                            String fieldUpdate = propertyHandlerUpdate(modelClass);
                            script = (String)method.invoke(cls.newInstance(),table,fieldUpdate+CONDITION);
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    createCommandStatement(configuration,sqlCommandType,modelClass,script,id);
                }
            }
        }

    }


    /**
     * 属性处理
     */
    private String propertyHandler(Class<?> modelClass) {
        StringBuffer stringBuffer = new StringBuffer();
        Field[] declaredFields = modelClass.getDeclaredFields();
        for (Field field:declaredFields){
            if(field.isAnnotationPresent(Column.class)){
                Column column = field.getAnnotation(Column.class);
                PROPERTIESMAP.put(field.getName(),column.name());
                stringBuffer.append(column.name()).append(",");
            }
        }
        return stringBuffer.deleteCharAt(stringBuffer.length() - 1).toString();
    }


    /**
     * 属性处理
     */
    private String propertyHandlerInsert(Class<?> modelClass) {
        StringBuffer stringBuffer = new StringBuffer();
        Field[] declaredFields = modelClass.getDeclaredFields();
        for (Field field:declaredFields){
            if(field.isAnnotationPresent(Column.class)){
                stringBuffer.append("#{"+field.getName()+"}").append(",");
            }
        }
        return stringBuffer.deleteCharAt(stringBuffer.length() - 1).toString();
    }

    /**
     * 属性处理
     */
    private String propertyHandlerUpdate(Class<?> modelClass) {
        StringBuffer stringBuffer = new StringBuffer();
        Field[] declaredFields = modelClass.getDeclaredFields();
        for (Field field:declaredFields){
            if(field.isAnnotationPresent(Column.class)){
                Column column = field.getAnnotation(Column.class);
                stringBuffer.append(column.name()+"=#{"+field.getName()+"}").append(",");
            }
        }
        return stringBuffer.deleteCharAt(stringBuffer.length() - 1).toString();
    }

    /**
     * 提取泛型模型,多泛型的时候请将泛型T放在第一位
     *
     * @param mapperClass mapper 接口
     * @return mapper 泛型
     */
    protected Class<?> extractModelClass(Class<?> mapperClass) {
        Type[] types = mapperClass.getGenericInterfaces();
        ParameterizedType target = null;
        for (Type type : types) {
            if (type instanceof ParameterizedType) {
                Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments();
                if (typeArray != null && typeArray.length > 0) {
                    for (Type t : typeArray) {
                        if (t instanceof TypeVariable || t instanceof WildcardType) {
                            break;
                        } else {
                            target = (ParameterizedType) type;
                            break;
                        }
                    }
                }
                break;
            }
        }
        return target == null ? null : (Class<?>) target.getActualTypeArguments()[0];
    }


    public static ConcurrentHashMap<String, Map<String,Object>> getPropertiesMap(){
        return MODEL_REPOSITORY_CACHE;
    }
}
